/* eslint-disable no-unused-vars, no-console, no-process-env */
// path alias module - rules config in ./package.json
require('module-alias/register')
const path = require('path')
const http = require('http')
const _ = require('lodash')

// middleware import
const json = require('koa-json')
const serve = require('koa-static')
const send = require('koa-send')
const koaBody = require('koa-body')
const compress = require('koa-compress')
const koaQs = require('koa-qs')
const uaParser = require('ua-parser-js')
// config
const config = require('./config')

// koa
const Koa = require('koa')
const app = new Koa()
const { allModelInit, models } = require('./src/model')

// 数据库初始化
async function initDb () {
  await allModelInit()
}

// 初始化上下文挂载
async function initContext () {
  // 启用反向代理模式
  // 解决 HTTP Cookie secure 状态，内部反向代理非 HTTPS 协议问题
  // 原理：检查是否存在 Header X-Forwarded-Proto，如果存在则视为 secure
  // 代理服务器 Nginx 配置示例：proxy_set_header X-Forwarded-Proto https;
  app.proxy = true

  // AJV - 用于 JSON 校验
  const Ajv = require('ajv')
  const ajv = new Ajv({
    allErrors: true,
    removeAdditional: true
    // schemas: [schemas],
  })
  const _validate = (validate) => function (data) {
    const valid = validate(data)
    if (valid) return { valid: true }
    return { valid: false, errorsText: ajv.errorsText(validate.errors) }
  }
  app.context.validatorCompile = schema => {
    const schemaValidate = ajv.compile(schema)
    return _validate(schemaValidate)
  }
}

// 中间件初始化
function initMiddleware () {
  // koa qs
  koaQs(app, 'extended', {
    arrayFormat: 'brackets'
  })

  // error
  app.use(async function (ctx, next) {
    try {
      await next()
    } catch (err) {
      let isExpect = false
      let error = err
      // expected error
      if (_.isString(error)) {
        isExpect = true
        error = new Error(error)
      }
      // unexpected handle error
      ctx.status = error.status || 500
      const accept = ctx.accepts(['html', 'json']).toString()
      switch (accept) {
        case 'html':
        case 'json':
          ctx.status = 200
          ctx.body = {
            code: error.code || 4,
            msg: `SERVER ERROR: ${error.message}`,
            stack: error.stack
          }
          break
        default:
          ctx.type = 'text'
          ctx.body = `${JSON.stringify(error.stack)}`
      }
      !isExpect && ctx.app.emit('error', error, ctx)
    }
  })

  // GZIP
  app.use(compress({
    filter (contentType) {
      // 过滤类型
      // text/html
      // text/plain
      // text/css
      // text/**
      // application/javascript
      // application/xml
      // application/json
      // application/**
      return /text|application/i.test(contentType)
    },
    threshold: 1024 * 32, // 大于 32KB 则启用压缩
    gzip: {
      flush: require('zlib').constants.Z_SYNC_FLUSH
    },
    deflate: {
      flush: require('zlib').constants.Z_SYNC_FLUSH
    },
    br: true // disable brotli
  }))

  // 支持跨域访问控制CORS配置
  app.use(function (ctx, next) {
    if (!ctx.header.origin) return next()
    if (/localhost|51tixiao.com|51tixiao.cn|8.140.147.197|81.70.152.40|192.168.1.17|51startup.cn/.test(ctx.header.origin)) {
      const allowOrigin = ctx.header.origin
      ctx.set('Access-Control-Allow-Origin', allowOrigin)
    }
    ctx.set('Access-Control-Allow-Credentials', 'true')
    ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Cookie, x-access-token')
    ctx.set('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,PATCH,OPTIONS')
    if (ctx.method === 'OPTIONS') {
      ctx.status = 200
      return
    }
    return next()
  })

  // middleware assembly
  app.use(/** @type {*} */json())
  app.use(/** @type {*} */koaBody({
    multipart: true,
    formidable: {
      uploadDir: path.resolve(__dirname, './uploads'),
      keepExtensions: true,
      maxFieldsSize: 10 * 1024 * 1024, // 10mb
      maxFileSize: 10 * 1024 * 1024 * 1024,
      hashAlgorithm: true
    }
  }))

  // HTTP Log
  app.use(async (ctx, next) => {
    console.log(ctx.method, ctx.originalUrl)
    ~['POST', 'PUT', 'PATCH'].indexOf(ctx.method) && console.log('RequestBody: ', ctx.request.body)
    let processError = null
    const requestAt = new Date()
    const ip = ctx.request.headers['x-real-ip'] ||
      ctx.request.headers['x-forwarded-for'] ||
      ctx.request.ip
    const userAgent = ctx.request.headers['user-agent']
    const uaInfo = uaParser(userAgent)

    // 用户请求分析数据附加到 ctx
    ctx._ip = ip
    ctx._uaInfo = uaInfo

    const requestData = {
      ip: ip,
      userAgent: userAgent,
      browser: uaInfo.browser,
      os: uaInfo.os,
      originalUrl: ctx.originalUrl,
      method: ctx.method,
      requestBody: ctx.request.body,
      requestAt,
      host: ctx.header.host,
      referer: ctx.header.referer
    }

    try {
      await next()
    } catch (err) {
      processError = err
      throw err
    } finally {
      const user = ctx._user || ctx._tUser
      const responseAt = new Date()
      const responseTime = responseAt - requestAt
      if (user) {
        requestData.userId = user._id
        requestData.userName = user.name
      }
      requestData.responseAt = responseAt
      requestData.responseTime = responseTime
      models.SystemRequestLog.insertOne(requestData)
      if (processError) {
        models.SystemErrorLog.insertOne({
          name: processError.name,
          message: processError.message,
          stack: processError.stack,
          request: requestData
        })
      }
    }
  })

  // routes
  const appsRouter = require('./src/routes')
  app.use(appsRouter.routes())

  // static:
  // ./public/
  const staticPath = path.resolve(__dirname, '../client/dist/spa')
  const publicServe = serve(staticPath, {
    maxage: 1000 * 3600 * 24 * 365
  })
  app.use(async (ctx, next) => {
    if (ctx.originalUrl === '/') {
      return send(ctx, 'index.html', { root: staticPath })
    }
    return publicServe(ctx, next)
  })

  app.use(async (ctx, next) => {
    if (/\/v\d*/.test(ctx.path)) {
      return next()
    }
    await send(ctx, 'index.html', { root: staticPath })
  })

  // 404
  app.use(function (ctx) {
    ctx.status = 404
    switch (ctx.accepts('html', 'json')) {
      case 'html':
        ctx.type = 'html'
        ctx.body = '<p>Page Not Found</p>'
        break
      case 'json':
        ctx.body = {
          msg: 'Page Not Found'
        }
        break
      default:
        ctx.type = 'text'
        ctx.body = 'Page Not Found'
    }
  })
}

// 服务初始化
(async function init () {
  // 初始化数据库连接
  await initDb()
  console.log('Init db success')
  // 初始化 Context
  await initContext()
  console.log('Init context success')
  // 初始化中间件
  initMiddleware()
  console.log('Init middleware success')
  // 启动服务
  const server = http.createServer(app.callback())
  const port = config.app.port

  function onError (error) {
    if (error.syscall !== 'listen') {
      throw error
    }
    const bind = typeof port === 'string'
      ? 'Pipe ' + port
      : 'Port ' + port

    // handle specific listen errors with friendly messages
    switch (error.code) {
      case 'EACCES':
        console.error(bind + ' requires elevated privileges')
        return process.exit(1)
      case 'EADDRINUSE':
        console.error(bind + ' is already in use')
        return process.exit(1)
      default:
        throw error
    }
  }

  function onListening () {
    const addr = server.address()
    const bind = typeof addr === 'string'
      ? 'pipe ' + addr
      : 'port ' + addr.port
    console.info(`Listening on ${bind}  http://localhost:${addr.port}`)
  }

  server.listen(port)
  server.on('error', onError)
  server.on('listening', onListening)
})()

module.exports = app
